﻿// ---------------------------------------------------------------------------------------------------------------------
//   Copyright (c) SRT Solutions 2009. All rights reserved.
// ---------------------------------------------------------------------------------------------------------------------

using System;

using NUnit.Framework;

using SRTSolutions.Elevate.FuncExtensions;

namespace SRTSolutions.Elevate.Guide
{
    public class CurryingAndPartialApplication
    {
        /// <summary>
        /// Currying means to take a function that accepts multiple arguments
        /// and transform it into series of functions that take one argument
        /// and return a function that accepts one argument until we get
        /// the result.
        /// </summary>
        [Test]
        public void WhatIsCurrying()
        {
            //given a function that takes two arguments
            Func<int, int, int> multiply = (x, y) => x * y;

            //we can curry  like this
            Func<int, Func<int, int>> curried = multiply.Curry();

            //our result is a function that accepts one argument
            //and returns a function accepting one argument
            Func<int, int> anotherFunction = curried(3);

            //when we apply this function, we get an integer result
            int result = anotherFunction(5);

            //this result is equal to the result of applying the arguments
            //to the original uncurried function.
            Assert.AreEqual(multiply(3, 5), result);
        }

        /// <summary>
        /// Partial function application takes a function with n arguments
        /// and a value to "bind" one of the arguments with. It returns a
        /// function that takes n-1 arguments. The returned function will 
        /// return the same value as the source function, except that one
        /// of the arguments is already specified.
        /// </summary>
        [Test]
        public void PartialFunctionApplication()
        {
            //given a function that takes two arguments
            Func<int, int, int> divide = (x, y) => x / y;

            //we can partially apply it like this
            Func<int, int> partiallyApplied = divide.ApplyFirst(4);

            //the result function does the same thing that the source
            //function does, but it only takes one argument (the second
            //argument)
            Assert.AreEqual(divide(4, 5), partiallyApplied(5));
        }

        [Test]
        public void PartialFunctionApplicationSecondArgument()
        {
            //given a function that takes two arguments
            Func<int, int, int> divide = (x, y) => x / y;

            //we saw above that we can partially apply the argument to the function.
            //we can also partially apply the second argument:
            Func<int, int> partiallyApplied = divide.ApplySecond(5);

            Assert.AreEqual(divide(10, 5), partiallyApplied(10));
        }


        #region Unit Tests

        #region Currying

        [Test]
        public void CurryingFunc2Exception()
        {
            Func<int, int, int> tmp = null;
            Assert.Throws<ArgumentNullException>(() => tmp.Curry());
        }

        [Test]
        public void CurryingFunc3()
        {
            Func<int, int, int, int> multiply = (x, y, z) => x * y * z;
            var curried = multiply.Curry();

            var result = curried(2)(4)(6);
            Assert.AreEqual(multiply(2, 4, 6), result);
        }

        [Test]
        public void CurryingFunc3Exception()
        {
            Func<int, int, int, int> tmp = null;
            Assert.Throws<ArgumentNullException>(() => tmp.Curry());
        }

        [Test]
        public void CurryingFunc4()
        {
            Func<int, int, int, int, int> multiply = (x, y, z, a) => x * y * z * a;
            var curried = multiply.Curry();

            var result = curried(2)(4)(6)(8);
            Assert.AreEqual(multiply(2, 4, 6, 8), result);
        }

        [Test]
        public void CurryingFunc4Exception()
        {
            Func<int, int, int, int, int> tmp = null;
            Assert.Throws<ArgumentNullException>(() => tmp.Curry());
        }

        [Test]
        public void CurryingAction2()
        {
            int result = 0;
            Action<int, int> tmp = (x, y) => result = x * y;

            var curried = tmp.Curry();
            curried(5)(10);

            Assert.AreEqual(result, 50);
        }

        [Test]
        public void CurryingAction2Exception()
        {
            Action<int, int> tmp = null;
            Assert.Throws<ArgumentNullException>(() => tmp.Curry());
        }

        [Test]
        public void CurryingAction3()
        {
            int result = 0;
            Action<int, int, int> tmp = (x, y, z) => result = x * y * z;

            var curried = tmp.Curry();
            curried(5)(10)(15);

            Assert.AreEqual(result, 750);
        }

        [Test]
        public void CurryingAction3Exception()
        {
            Action<int, int, int> tmp = null;
            Assert.Throws<ArgumentNullException>(() => tmp.Curry());
        }

        [Test]
        public void CurryingAction4()
        {
            int result = 0;
            Action<int, int, int, int> tmp = (x, y, z, a) => result = x * y * z * a;

            var curried = tmp.Curry();
            curried(5)(10)(15)(20);

            Assert.AreEqual(result, 15000);
        }

        [Test]
        public void CurryingAction4Exception()
        {
            Action<int, int, int, int> tmp = null;
            Assert.Throws<ArgumentNullException>(() => tmp.Curry());
        }

        #endregion

        [Test]
        public void PartialApplyFunc2ThisException()
        {
            Func<int, int, int> tmp = null;
            Assert.Throws<ArgumentNullException>(() => tmp.ApplyFirst(10));
        }

        [Test]
        public void PartialApplyFunc2ArgumentException()
        {
            Func<string, string, string> tmp = (x,y) => x + y;
            Assert.Throws<ArgumentNullException>(() => tmp.ApplyFirst(null));
        }

        [Test]
        public void PartialApplySecondFunc2ThisException()
        {
            Func<int, int, int> tmp = null;
            Assert.Throws<ArgumentNullException>(() => tmp.ApplySecond(10));
        }

        [Test]
        public void PartialApplySecondFunc2ArgumentException()
        {
            Func<string, string, string> tmp = (x,y) => x + y;
            Assert.Throws<ArgumentNullException>(() => tmp.ApplySecond(null));
        }

        [Test]
        public void PartialApplyFirstAction2()
        {
            var first = 0;
            var second = "";

            Action<int, string> tmp = (one, two) => { first = one; second = two; };

            var applied = tmp.ApplyFirst(10);
            applied("test");

            Assert.AreEqual(10, first);
            Assert.AreEqual("test", second);
        }

        [Test]
        public void PartialApplyFirstAction2ThisException()
        {
            Action<int, int> tmp = null;
            Assert.Throws<ArgumentNullException>(() => tmp.ApplyFirst(10));
        }

        [Test]
        public void PartialApplyFirstAction2ArgumentException()
        {
            Action<string, string> tmp = (x, y) => { var ignored = x; };
            Assert.Throws<ArgumentNullException>(() => tmp.ApplyFirst(null));
        }

        [Test]
        public void PartialApplySecondAction2()
        {
            var first = 0;
            var second = "";

            Action<int, string> tmp = (one, two) => { first = one; second = two; };

            var applied = tmp.ApplySecond("test");
            applied(10);

            Assert.AreEqual(10, first);
            Assert.AreEqual("test", second);
        }

        [Test]
        public void PartialApplySecondAction2ThisException()
        {
            Action<int, int> tmp = null;
            Assert.Throws<ArgumentNullException>(() => tmp.ApplySecond(10));
        }

        [Test]
        public void PartialApplySecondAction2ArgumentException()
        {
            Action<string, string> tmp = (x, y) => { var ignored = x; };
            Assert.Throws<ArgumentNullException>(() => tmp.ApplySecond(null));
        }

        [Test]
        public void PartiallyApplyFirstFunc3()
        {
            Func<int, int, int, int> function = (x, y, z) => x / y * z;

            Func<int, int, int> partiallyApplied = function.ApplyFirst(50);

            Assert.AreEqual(function(50, 10, 2), partiallyApplied(10, 2));
        }

        [Test]
        public void PartiallyApplyFirstFunc3ThisException()
        {
            Func<int, int, int, int> function = null;
            Assert.Throws<ArgumentNullException>(() => function.ApplyFirst(10));
        }

        [Test]
        public void PartiallyApplyFirstFunc3ArgumentException()
        {
            Func<string, string, string, string> function = (x, y, z) => x + y + z;

            Assert.Throws<ArgumentNullException>(() => function.ApplyFirst(null));
        }

        [Test]
        public void PartiallyApplySecondFunc3()
        {
            Func<int, int, int, int> function = (x, y, z) => x / y * z;

            Func<int, int, int> partiallyApplied = function.ApplySecond(10);

            Assert.AreEqual(function(50, 10, 2), partiallyApplied(50, 2));
        }

        [Test]
        public void PartiallyApplySecondFunc3ThisException()
        {
            Func<int, int, int, int> function = null;
            Assert.Throws<ArgumentNullException>(() => function.ApplySecond(10));
        }

        [Test]
        public void PartiallyApplySecondFunc3ArgumentException()
        {
            Func<string, string, string, string> function = (x, y, z) => x + y + z;

            Assert.Throws<ArgumentNullException>(() => function.ApplySecond(null));
        }

        [Test]
        public void PartiallyApplyThirdFunc3()
        {
            Func<int, int, int, int> function = (x, y, z) => x / y * z;

            Func<int, int, int> partiallyApplied = function.ApplyThird(2);

            Assert.AreEqual(function(50, 10, 2), partiallyApplied(50, 10));
        }

        [Test]
        public void PartiallyApplyThirdFunc3ThisException()
        {
            Func<int, int, int, int> function = null;
            Assert.Throws<ArgumentNullException>(() => function.ApplyThird(10));
        }

        [Test]
        public void PartiallyApplyThirdFunc3ArgumentException()
        {
            Func<string, string, string, string> function = (x, y, z) => x + y + z;

            Assert.Throws<ArgumentNullException>(() => function.ApplyThird(null));
        }

        [Test]
        public void PartiallyApplyFirstAction3()
        {
            var one = 0;
            var two = "";
            var three = 0;

            Action<int, string, int> action = (x, y, z) => { one = x; two = y; three = z; };

            var applied = action.ApplyFirst(15);
            applied("test", 123);

            Assert.AreEqual(15, one);
            Assert.AreEqual("test", two);
            Assert.AreEqual(123, three);
        }

        [Test]
        public void PartiallyApplyFirstAction3ThisException()
        {
            Action<int, int, int> function = null;
            Assert.Throws<ArgumentNullException>(() => function.ApplyFirst(10));
        }

        [Test]
        public void PartiallyApplyFirstAction3ArgumentException()
        {
            Action<string, string, string> function = (x, y, z) => { var tmp = x; };
            Assert.Throws<ArgumentNullException>(() => function.ApplyFirst(null));
        }

        [Test]
        public void PartiallyApplySecondAction3()
        {
            var one = 0;
            var two = "";
            var three = 0;

            Action<int, string, int> action = (x, y, z) => { one = x; two = y; three = z; };

            var applied = action.ApplySecond("test");
            applied(15, 123);

            Assert.AreEqual(15, one);
            Assert.AreEqual("test", two);
            Assert.AreEqual(123, three);
        }

        [Test]
        public void PartiallyApplySecondAction3ThisException()
        {
            Action<int, int, int> function = null;
            Assert.Throws<ArgumentNullException>(() => function.ApplySecond(10));
        }

        [Test]
        public void PartiallyApplySecondAction3ArgumentException()
        {
            Action<string, string, string> function = (x, y, z) => { var tmp = x; };
            Assert.Throws<ArgumentNullException>(() => function.ApplySecond(null));
        }

        [Test]
        public void PartiallyApplyThirdAction3()
        {
            var one = 0;
            var two = "";
            var three = 0;

            Action<int, string, int> action = (x, y, z) => { one = x; two = y; three = z; };

            var applied = action.ApplyThird(123);
            applied(15, "test");

            Assert.AreEqual(15, one);
            Assert.AreEqual("test", two);
            Assert.AreEqual(123, three);
        }

        [Test]
        public void PartiallyApplyThirdAction3ThisException()
        {
            Action<int, int, int> function = null;
            Assert.Throws<ArgumentNullException>(() => function.ApplyThird(10));
        }

        [Test]
        public void PartiallyApplyThirdAction3ArgumentException()
        {
            Action<string, string, string> function = (x, y, z) => { var tmp = x; };
            Assert.Throws<ArgumentNullException>(() => function.ApplyThird(null));
        }

        #endregion
    }
}
